/*
 * Copyright 2020-2099 sa-token.cc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.com.idmy.auth.context;

import cn.com.idmy.auth.exception.AuthException;
import cn.com.idmy.base.exception.BizException;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

@AllArgsConstructor
public class RequestForServlet implements Request {
    protected final HttpServletRequest request;

    @Override
    public @NotNull Object source() {
        return request;
    }

    @Override
    public @NotNull String body() {
        try (var reader = request.getReader()) {
            return IoUtil.read(reader);
        } catch (IOException e) {
            throw new BizException(e);
        }
    }

    @Override
    @Nullable
    public String getNullableParam(@NotNull String name) {
        return request.getParameter(name);
    }

    @Override
    public @NotNull List<String> paramNames() {
        var parameterNames = request.getParameterNames();
        var ls = new ArrayList<String>();
        while (parameterNames.hasMoreElements()) {
            ls.add(parameterNames.nextElement());
        }
        return ls;
    }

    @Override
    public @NotNull JSONObject params() {
        Map<String, String[]> parameterMap = request.getParameterMap();
        JSONObject map = new JSONObject(parameterMap.size());
        for (String key : parameterMap.keySet()) {
            String[] values = parameterMap.get(key);
            map.put(key, values[0]);
        }
        return map;
    }

    @Override
    public @NotNull Collection<String> headerNames() {
        var headerNames = request.getHeaderNames();
        var ls = new ArrayList<String>();
        while (headerNames.hasMoreElements()) {
            ls.add(headerNames.nextElement());
        }
        return ls;
    }

    @Override
    public @NotNull Collection<String> headerValues() {
        return headerNames().stream().map(request::getHeader).toList();
    }

    @Override
    public @NotNull JSONObject headers() {
        var map = new JSONObject(headerNames().size());
        var names = headerNames();
        for (String name : names) {
            map.put(name, request.getHeader(name));
        }
        return map;
    }

    @Override
    @Nullable
    public String getHeader(@NotNull String name) {
        return request.getHeader(name);
    }

    @Override
    @Nullable
    public String getCookieValue(@NotNull String name) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie != null && name.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    @Override
    public @NotNull String uri() {
        return request.getRequestURI();
    }

    @Override
    public @NotNull String url() {
        return request.getRequestURL().toString();
    }

    @Override
    public @NotNull String method() {
        return request.getMethod();
    }

    @Override
    public @Nullable Object forward(@NotNull String path) {
        try {
            HttpServletResponse response = (HttpServletResponse) Contexts.context().response().source();
            request.getRequestDispatcher(path).forward(request, response);
            return null;
        } catch (ServletException | IOException e) {
            throw new AuthException(e.getMessage(), e);
        }
    }

    @Override
    public @NotNull String ipv4() {
        var ip = request.getHeader("X-Forwarded-For");
        if (StrUtil.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
            int idx = ip.indexOf(",");
            return idx != -1 ? ip.substring(0, idx) : ip;
        }
        ip = request.getHeader("X-Real-IP");
        if (StrUtil.isNotBlank(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    @Override
    public @NotNull String ipv6() {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim(); // 取第一个 IP 地址
        }
        return ip;
    }

}
